"""
FFmpeg视频切片服务

基于demo.py的VideoUtils.ffmpeg_slice_media方法封装的专业视频切片服务。
"""

import asyncio
import os
import json
import math
import tempfile
import shutil
from typing import Dict, List, Any, Optional, Tuple
from pathlib import Path
from datetime import datetime, timedelta
from dataclasses import dataclass
import logging
from src.core.di import Injectable, Inject
try:
    from ffmpeg import FFmpeg
    from ffmpeg.asyncio import FFmpeg as AsyncFFmpeg
    FFMPEG_PYTHON_AVAILABLE = True
except ImportError:
    FFMPEG_PYTHON_AVAILABLE = False
    FFmpeg = None
    AsyncFFmpeg = None

from loguru import logger


@dataclass
class SliceSegment:
    """切片段配置"""
    start: float  # 开始时间（秒）
    end: float    # 结束时间（秒）
    
    @property
    def duration(self) -> float:
        """片段时长"""
        return self.end - self.start
    
    def to_timedelta(self) -> Tuple[timedelta, timedelta]:
        """转换为timedelta格式"""
        return (
            timedelta(seconds=self.start),
            timedelta(seconds=self.end)
        )


@dataclass
class SliceOptions:
    """切片输出选项"""
    width: Optional[int] = None      # 输出宽度
    height: Optional[int] = None     # 输出高度
    crf: int = 23                    # 视频质量 (18-28, 越小质量越好)
    fps: int = 30                    # 输出帧率
    bit_rate: Optional[str] = None   # 比特率 (如 "2M")
    limit_size: Optional[str] = None # 文件大小限制 (如 "10M")
    preset: str = "medium"           # 编码预设 (ultrafast, fast, medium, slow, veryslow)
    
    @property
    def pretty_bit_rate(self) -> str:
        """格式化的比特率"""
        return self.bit_rate or "2M"


@dataclass
class VideoMetadata:
    """视频元数据"""
    duration: float
    width: int
    height: int
    fps: float
    format_name: str
    size: int
    codec_name: str
    audio_codec: str
    
    @classmethod
    def from_ffprobe(cls, metadata: dict) -> 'VideoMetadata':
        """从ffprobe结果创建元数据"""
        format_info = metadata.get('format', {})
        video_stream = None
        audio_stream = None

        for stream in metadata.get('streams', []):
            if stream.get('codec_type') == 'video':
                video_stream = stream
            elif stream.get('codec_type') == 'audio':
                audio_stream = stream

        if not video_stream:
            raise ValueError("No video stream found")

        # 解析帧率
        r_frame_rate = video_stream.get('r_frame_rate', '30/1')
        if '/' in r_frame_rate:
            num, den = map(int, r_frame_rate.split('/'))
            fps = num / den if den != 0 else 30.0
        else:
            fps = float(r_frame_rate)

        # 获取音频编码器信息
        audio_codec = audio_stream.get('codec_name', '') if audio_stream else ''

        return cls(
            duration=float(format_info.get('duration', 0)),
            width=int(video_stream.get('width', 0)),
            height=int(video_stream.get('height', 0)),
            fps=fps,
            format_name=format_info.get('format_name', 'unknown'),
            size=int(format_info.get('size', 0)),
            codec_name=video_stream.get('codec_name', 'unknown'),
            audio_codec=audio_codec
        )

@Injectable()
class FfmpegSliceService:
    """
    FFmpeg视频切片服务
    
    提供专业的视频切片功能，支持按时间段切割、质量控制、批量处理等。
    """
    
    def __init__(self, logger: logging.Logger = Inject("logger")):
        self.logger = logger or logging.getLogger(__name__)
        
        if not FFMPEG_PYTHON_AVAILABLE:
            self.logger.warning("python-ffmpeg not available, some features may be limited")
    
    def _create_async_ffmpeg_cmd(self, quiet: bool = False) -> Optional[AsyncFFmpeg]:
        """创建异步FFmpeg命令对象"""
        if not FFMPEG_PYTHON_AVAILABLE:
            return None
            
        ffmpeg_cmd = AsyncFFmpeg().option('y').option('hide_banner')

        @ffmpeg_cmd.on("start")
        def on_start(arguments: list[str]):
            try:
                filter_index = arguments.index("-filter_complex")
                filter_content = arguments[filter_index + 1]
                arguments[filter_index + 1] = f'"{filter_content}"'
                args = " ".join(arguments)
                arguments[filter_index + 1] = filter_content
            except ValueError:
                args = " ".join(arguments)
                logger.info(f"FFmpeg command: {args}")

        @ffmpeg_cmd.on("progress")
        def on_progress(progress):
            if not quiet:
                logger.info(f"处理进度: {progress}")

        @ffmpeg_cmd.on("completed")
        def on_completed():
            logger.info(f"FFmpeg task completed.")

        @ffmpeg_cmd.on("stderr")
        def on_stderr(line: str):
            if line.startswith('Error') and ".m3u8" not in line:
                raise RuntimeError(line)
            elif "Output file is empty" in line:
                raise RuntimeError("输出是空文件")
            else:
                ...

        return ffmpeg_cmd
    
    async def get_video_metadata(self, media_path: str) -> VideoMetadata:
        """
        获取视频元数据
        
        Args:
            media_path: 视频文件路径
            
        Returns:
            VideoMetadata: 视频元数据对象
        """
        if not FFMPEG_PYTHON_AVAILABLE:
            raise RuntimeError("python-ffmpeg required for metadata extraction")
        
        ffprobe = AsyncFFmpeg('ffprobe').input(
            media_path, 
            print_format="json",
            show_streams=None, 
            show_format=None
        )
        
        try:
            metadata_bytes = await ffprobe.execute()
            metadata_json = json.loads(metadata_bytes)
            logger.info(f"获取视频元数据: {media_path}")
            return VideoMetadata.from_ffprobe(metadata_json)
        except Exception as e:
            logger.error(f"Failed to get video metadata: {e}")
            raise
    
    def _validate_segments(self, segments: List[SliceSegment], video_duration: float) -> None:
        """
        验证切片段配置
        
        Args:
            segments: 切片段列表
            video_duration: 视频总时长
        """
        diff_tolerance = 0.001
        
        for i, segment in enumerate(segments):
            if segment.start > video_duration or segment.start < 0:
                raise ValueError(
                    f"第{i}个切割点起始点{segment.start}s超出视频时长[0-{video_duration}s]范围"
                )
            
            if segment.end > video_duration or segment.end < 0:
                if segment.end > 0 and math.isclose(segment.end, video_duration, rel_tol=diff_tolerance):
                    # 允许小的误差
                    segment.end = video_duration
                    logger.warning(
                        f"第{i}个切割点结束点{segment.end}s接近视频时长，已调整为{video_duration}s"
                    )
                else:
                    raise ValueError(
                        f"第{i}个切割点结束点{segment.end}s超出视频时长[0-{video_duration}s]范围"
                    )
            
            if segment.start >= segment.end:
                raise ValueError(
                    f"第{i}个切割点起始时间{segment.start}s必须小于结束时间{segment.end}s"
                )
    
    def _generate_output_path(self, base_path: str, index: int, extension: str = "mp4") -> str:
        """生成输出文件路径"""
        base = Path(base_path)
        return str(base.parent / f"{base.stem}_{index:03d}.{extension}")
    
    async def slice_video(self,
                         media_path: str,
                         segments: List[SliceSegment],
                         options: SliceOptions,
                         output_path: Optional[str] = None) -> List[Tuple[str, VideoMetadata]]:
        """
        使用本地视频文件按时间段切割出分段视频
        
        Args:
            media_path: 本地视频路径
            segments: 分段起始结束时间标记列表
            options: 输出切割质量选项
            output_path: 最终输出文件路径，片段会根据指定路径附加_001.mp4等片段编号
            
        Returns:
            List[Tuple[str, VideoMetadata]]: 输出片段的本地路径和元数据
        """
        if not FFMPEG_PYTHON_AVAILABLE:
            raise RuntimeError("python-ffmpeg required for video slicing")
        
        if not segments:
            raise ValueError("No segments provided")
        
        # 获取视频元数据
        metadata = await self.get_video_metadata(media_path)
        logger.info(f"视频信息: {metadata.width}x{metadata.height}, {metadata.duration:.2f}s, {metadata.fps}fps")
        
        # 验证切片段
        self._validate_segments(segments, metadata.duration)
        
        # 准备输出路径
        if not output_path:
            output_path = str(Path(media_path).with_suffix('_slice.mp4'))
        
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        
        # 创建FFmpeg命令
        ffmpeg_cmd = self._create_async_ffmpeg_cmd()
        if not ffmpeg_cmd:
            raise RuntimeError("Failed to create FFmpeg command")
        
        ffmpeg_cmd.input(media_path)
        
        # 检查是否有音频流
        has_audio = metadata.audio_codec is not None and metadata.audio_codec != ""

        # 构建filter_complex
        filter_complex = []
        temp_outputs = []

        for index, segment in enumerate(segments):
            start = segment.start
            end = segment.end

            # 处理指定的输出分辨率
            if options.width and options.height:
                filter_complex.append(f"[v:0]trim=start={start}:end={end},scale={options.width}:{options.height},setpts=PTS-STARTPTS[cut{index}]")
                if has_audio:
                    filter_complex.append(f"[a:0]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[acut{index}]")
            else:
                filter_complex.append(f"[v:0]trim=start={start}:end={end},setpts=PTS-STARTPTS[cut{index}]")
                if has_audio:
                    filter_complex.append(f"[a:0]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[acut{index}]")

        ffmpeg_cmd.option('filter_complex', ';'.join(filter_complex))
        
        # 为每个片段配置输出
        for i, segment in enumerate(segments):
            segment_output_path = self._generate_output_path(output_path, i)

            # 根据是否有音频流配置映射
            if has_audio:
                map_options = [f"[cut{i}]", f"[acut{i}]"]
            else:
                map_options = [f"[cut{i}]"]

            ffmpeg_options = {
                "map": map_options,
                "reset_timestamps": "1",
                "sc_threshold": "0",
                "g": "1",
                "force_key_frames": "expr:gte(t,n_forced*1)",
                "vcodec": "libx264",
                "crf": options.crf,
                "r": options.fps,
                "preset": options.preset
            }

            # 只有在有音频时才添加音频编码器
            if has_audio:
                ffmpeg_options["acodec"] = "aac"
            
            if options.limit_size:
                ffmpeg_options["fs"] = options.limit_size
            elif options.bit_rate:
                ffmpeg_options["b:v"] = options.pretty_bit_rate
            
            ffmpeg_cmd.output(segment_output_path, options=ffmpeg_options)
            temp_outputs.append(segment_output_path)
        
        # 执行切片
        try:
            await ffmpeg_cmd.execute()
            logger.info(f"视频切片完成: 生成{len(temp_outputs)}个片段")
        except Exception as e:
            logger.error(f"视频切片失败: {e}")
            raise
        
        # 获取输出文件的元数据
        outputs = []
        for output_file in temp_outputs:
            try:
                output_metadata = await self.get_video_metadata(output_file)
                outputs.append((output_file, output_metadata))
            except Exception as e:
                logger.warning(f"无法获取输出文件元数据 {output_file}: {e}")
                # 创建一个基本的元数据对象
                basic_metadata = VideoMetadata(
                    duration=0, width=0, height=0, fps=0,
                    format_name="mp4", size=0, codec_name="h264"
                )
                outputs.append((output_file, basic_metadata))
        
        return outputs

    async def slice_video_by_duration(self,
                                     media_path: str,
                                     segment_duration: float,
                                     options: SliceOptions,
                                     output_dir: Optional[str] = None,
                                     overlap: float = 0.0) -> List[Tuple[str, VideoMetadata]]:
        """
        按固定时长切割视频

        Args:
            media_path: 输入视频路径
            segment_duration: 每段时长（秒）
            options: 输出选项
            output_dir: 输出目录
            overlap: 片段重叠时间（秒）

        Returns:
            输出片段列表
        """
        metadata = await self.get_video_metadata(media_path)

        if not output_dir:
            output_dir = str(Path(media_path).parent / f"{Path(media_path).stem}_segments")

        # 计算切片段
        segments = []
        current_start = 0.0

        while current_start < metadata.duration:
            end_time = min(current_start + segment_duration, metadata.duration)

            if end_time - current_start >= 1.0:  # 至少1秒的片段
                segments.append(SliceSegment(start=current_start, end=end_time))

            current_start += segment_duration - overlap

            if current_start >= metadata.duration:
                break

        logger.info(f"按{segment_duration}s时长切割，生成{len(segments)}个片段")

        # 使用基础切片方法
        output_path = os.path.join(output_dir, f"{Path(media_path).stem}_segment.mp4")
        return await self.slice_video(media_path, segments, options, output_path)

    async def slice_video_by_count(self,
                                  media_path: str,
                                  segment_count: int,
                                  options: SliceOptions,
                                  output_dir: Optional[str] = None) -> List[Tuple[str, VideoMetadata]]:
        """
        按片段数量平均切割视频

        Args:
            media_path: 输入视频路径
            segment_count: 片段数量
            options: 输出选项
            output_dir: 输出目录

        Returns:
            输出片段列表
        """
        if segment_count <= 0:
            raise ValueError("Segment count must be positive")

        metadata = await self.get_video_metadata(media_path)
        segment_duration = metadata.duration / segment_count

        if not output_dir:
            output_dir = str(Path(media_path).parent / f"{Path(media_path).stem}_segments")

        # 计算切片段
        segments = []
        for i in range(segment_count):
            start_time = i * segment_duration
            end_time = min((i + 1) * segment_duration, metadata.duration)

            if end_time - start_time >= 0.5:  # 至少0.5秒的片段
                segments.append(SliceSegment(start=start_time, end=end_time))

        logger.info(f"平均切割为{len(segments)}个片段，每段约{segment_duration:.2f}s")

        # 使用基础切片方法
        output_path = os.path.join(output_dir, f"{Path(media_path).stem}_segment.mp4")
        return await self.slice_video(media_path, segments, options, output_path)

    async def batch_slice_videos(self,
                                video_files: List[str],
                                segment_duration: float,
                                options: SliceOptions,
                                output_base_dir: str,
                                max_concurrent: int = 3) -> Dict[str, Any]:
        """
        批量切割多个视频

        Args:
            video_files: 视频文件列表
            segment_duration: 每段时长
            options: 输出选项
            output_base_dir: 输出基础目录
            max_concurrent: 最大并发数

        Returns:
            批处理结果
        """
        os.makedirs(output_base_dir, exist_ok=True)

        # 创建任务
        async def process_single_video(video_file: str):
            try:
                file_name = Path(video_file).stem
                output_dir = os.path.join(output_base_dir, file_name)

                results = await self.slice_video_by_duration(
                    media_path=video_file,
                    segment_duration=segment_duration,
                    options=options,
                    output_dir=output_dir
                )

                return {
                    "file": video_file,
                    "success": True,
                    "segments": len(results),
                    "outputs": [path for path, _ in results]
                }
            except Exception as e:
                logger.error(f"处理视频失败 {video_file}: {e}")
                return {
                    "file": video_file,
                    "success": False,
                    "error": str(e)
                }

        # 并发执行
        semaphore = asyncio.Semaphore(max_concurrent)

        async def limited_task(video_file):
            async with semaphore:
                return await process_single_video(video_file)

        logger.info(f"开始批量处理{len(video_files)}个视频文件")
        results = await asyncio.gather(
            *[limited_task(video_file) for video_file in video_files],
            return_exceptions=True
        )

        # 统计结果
        success_count = 0
        error_count = 0
        total_segments = 0
        errors = []

        for result in results:
            if isinstance(result, Exception):
                error_count += 1
                errors.append({"error": str(result)})
            elif result["success"]:
                success_count += 1
                total_segments += result["segments"]
            else:
                error_count += 1
                errors.append(result)

        batch_result = {
            "total_files": len(video_files),
            "success_count": success_count,
            "error_count": error_count,
            "total_segments": total_segments,
            "results": results,
            "errors": errors
        }

        logger.info(f"批量处理完成: 成功{success_count}, 失败{error_count}, 总片段{total_segments}")
        return batch_result

    def create_slice_options(self,
                           quality: str = "medium",
                           width: Optional[int] = None,
                           height: Optional[int] = None,
                           fps: int = 30,
                           bit_rate: Optional[str] = None,
                           limit_size: Optional[str] = None) -> SliceOptions:
        """
        创建切片选项的便捷方法

        Args:
            quality: 质量预设 (low, medium, high)
            width: 输出宽度
            height: 输出高度
            fps: 帧率
            bit_rate: 比特率
            limit_size: 文件大小限制

        Returns:
            SliceOptions对象
        """
        quality_presets = {
            "low": {"crf": 28, "preset": "fast"},
            "medium": {"crf": 23, "preset": "medium"},
            "high": {"crf": 18, "preset": "slow"},
            "ultra": {"crf": 15, "preset": "veryslow"}
        }

        preset_config = quality_presets.get(quality, quality_presets["medium"])

        return SliceOptions(
            width=width,
            height=height,
            crf=preset_config["crf"],
            fps=fps,
            bit_rate=bit_rate,
            limit_size=limit_size,
            preset=preset_config["preset"]
        )
